/*
 * 作者      : 马晓诺
 * 时间      : 2025年3月24日
 * 代码功能  :
 *    - 实现 ESP32-S3-DevKitC-1 与 ESP32 DEVKITV1 的 ESP-MESH 组网
 *    - ESP32 采集 DHT 传感器的温湿度数据，并通过 ESP-MESH 发送给 ESP32S3
 *    - ESP32S3 解析接收到的温湿度数据，并连接 2.4GHz WiFi，通过 MQTT 协议将温湿度数据上传到阿里云 IoT 服务器
 * 
 * 相关技术  :
 *    - ESP-MESH 无线组网
 *    - MQTT 通信协议
 *    - WiFi 连接与稳定性优化
 *    - 温湿度数据解析与上传
 */

#include <Arduino.h>
#include <WiFi.h>
#include "painlessMesh.h"
#include "PubSubClient.h"
// ESP-MESH 组网鉴权信息，只有信息相同的 ESP32 才可以进行 ESP-MESH 组网
#define   MESH_PREFIX     "LittleCoke"
#define   MESH_PASSWORD   "123456hello"
#define   MESH_PORT       5555
// 路由器信息，频率必须是 2.4GHz
#define WIFI_SSID         "LittleCoke's Xiaomi 15"
#define WIFI_PASSWD       "123456xiaokele"
// 设备三元组信息
// 具体修改详情请参考b站讲解：https://www.bilibili.com/video/BV1u34y1u79G?spm_id_from=333.788.videopod.sections&vd_source=2f59f48b624913b355669572efc91a2c
#define PRODUCT_KEY       "a1A5fKpG4kT"
#define DEVICE_NAME       "littlecoke"
#define DEVICE_SECRET     "2b37755bd87020f3a00d20f164d731fe"
#define REGION_ID         "cn-shanghai"
// MQTT连接参数
#define MQTT_SERVER       PRODUCT_KEY".iot-as-mqtt."REGION_ID ".aliyuncs.com"
#define MQTT_PORT         1883
#define MQTT_USRNAME      DEVICE_NAME"&"PRODUCT_KEY
#define CLIENT_ID         "a1A5fKpG4kT.littlecoke|securemode=2,signmethod=hmacsha256,timestamp=1743222810769|"
#define MQTT_PASSWD       "11eabc97387c7625e73b09d85e9ffaa96741e032fbdb03596ab8b3735eb177d8"

#define ALINK_BODY_FORMAT "{\"id\":\"littlecoke\",\"version\":\"1.0\",\"method\":\"thing.event.property.post\",\"params\":%s}"
#define ALINK_TOPIC_PROP_POST "/sys/" PRODUCT_KEY "/" DEVICE_NAME "/thing/event/property/post"

#define ESP32_NODE 3316138365
#define ESP32C3_NODE 2030530093
#define ESP32S3_NODE 412166304

WiFiClient espClient;               // 创建 WIFI 客户端对象
PubSubClient client(espClient);     // 创建一个 MQTT 客户端对象，通过 WIFI 客户端 espClient 连接 MQTT 服务器
painlessMesh mesh;                  // 实例化 mesh 对象，用于处理后续的各种信息收发
Scheduler userScheduler;            // 创建任务管理器，因为 ESP-MESH 要尽量避免使用延时函数delay()

float humidity = 0.0;
float temperature = 0.0;
float smoke = 0.0;

void checkWiFi();
void sendMessage();
void mqttCheckConnect();
void mqttIntervalPost();

// 任务调度
Task taskWifiCheck(TASK_SECOND * 7, TASK_FOREVER, &checkWiFi);
Task taskSendMessage(TASK_SECOND * 3, TASK_FOREVER, &sendMessage);
Task taskMqttPost(TASK_SECOND * 10, TASK_FOREVER, &mqttIntervalPost);
Task taskMqttCheck(TASK_SECOND * 5, TASK_FOREVER, &mqttCheckConnect);

// 发送测试消息
void sendMessage() {
    String msg = "ESP32S3 (Node:";
    msg += mesh.getNodeId();
    msg += ")has received Temperature, Humidity and Smoke concentration data";
    mesh.sendSingle(ESP32_NODE,msg);
    taskSendMessage.setInterval(TASK_SECOND * 1);
}
// 解析接收到的 ESP-MESH 数据，提取温湿度
// 收到msg信息示例：ESP32 Node 3316138365 | Temp: 24.10C | Hum: 30.00%RH | Smoke: 5.28ppm
void receivedCallback(uint32_t from, String &msg) {
    Serial.printf("startHere: Received from %u msg=%s\n", from, msg.c_str());

    int tempPos = msg.indexOf("Temp: ");
    int humPos = msg.indexOf("Hum: ");
    int smokePos = msg.indexOf("Smoke: ");

    if (tempPos != -1 && humPos != -1 && smokePos != -1) {
        temperature = msg.substring(tempPos + 6, msg.indexOf("C", tempPos)).toFloat();
        humidity = msg.substring(humPos + 5, msg.indexOf("%", humPos)).toFloat();
        smoke = msg.substring(smokePos + 7, msg.indexOf("ppm",smokePos)).toFloat();
        Serial.printf("Extracted Temp: %.2f C, Hum: %.2f RH, Somke: %.2f ppm\n", temperature, humidity,smoke);
    }
}

// 负责通知有没有新设备接入到 ESP-MESH 网络中
void newConnectionCallback(uint32_t nodeId) {
    Serial.printf("New Connection: nodeId = %u\n", nodeId);
  }

// 负责通知 ESP-MESH 网络连接出现变化，比如有设备离线或有新设备加入等
  void changedConnectionCallback() {
    Serial.println("Connections changed");
  }

// 负责打印时间同步信息，以确保 ESP-MESH 网络中所有设备的时间是同步的
  void nodeTimeAdjustedCallback(int32_t offset) {
    Serial.printf("Time adjusted: Offset = %d\n", offset);
  }
  

// WiFi 连接任务
void checkWiFi() {
    // 确保 ESP-MESH 已连接; bool isConnected(uint32_t nodeId)函数参数请输入另一块 ESP32 的 MESH_NodeId
    // 此处必须先进行组网，不然 mqttIntervalPost 函数会影响 ESP-MESH 组网进程
    // 原因可能是 ESP-MESH 是 WIFI 层面的协议，ESP32S3 需要同时运行 WiFi 和 ESP-MESH，而它们可能在底层共享相同的无线资源（如无线电模块），导致冲突
    // 当 MQTT 连接活跃时，可能会抢占 WiFi 资源，使 ESP-MESH 失去连接
    if (!mesh.isConnected(ESP32_NODE)) {
        Serial.println("ESP-MESH_ESP32 未连接，等待重试...");
        return;  // 如果 ESP-MESH 未连接，则直接返回
    }
    else if (WiFi.status() != WL_CONNECTED) {
        Serial.println("WiFi disconnected! Reconnecting...");
        WiFi.begin(WIFI_SSID, WIFI_PASSWD);
        delay(100);  //此处延时0.1s是因为WIFI连接需要一定时间
    }
}

// MQTT 连接检查
void mqttCheckConnect() {
    // 确保 WIFI 连接，然后进行 MQTT 连接
    if(WiFi.status() == WL_CONNECTED){
        if (!client.connected()) {
            Serial.println("Connecting to MQTT Server ...");
            if (client.connect(CLIENT_ID, MQTT_USRNAME, MQTT_PASSWD)) {
                Serial.println("MQTT Connected!");
            } else {
                Serial.print("MQTT Connect err:");
                Serial.println(client.state());
            }
        }
    }
}

// 上传温湿度数据
void mqttIntervalPost() {
    //确保 MQTT 连接成功，然后进行温湿度上传
    if(client.connected()){
        char param[64];
        char jsonBuf[128];

        sprintf(param, "{\"Humidity\":%.2f}", humidity);
        sprintf(jsonBuf, ALINK_BODY_FORMAT, param);
        boolean b = client.publish(ALINK_TOPIC_PROP_POST, jsonBuf);
        Serial.println(b ? "Publish Humidity success" : "Publish Humidity fail");

        sprintf(param, "{\"Temperature\":%.2f}", temperature);
        sprintf(jsonBuf, ALINK_BODY_FORMAT, param);
        boolean c = client.publish(ALINK_TOPIC_PROP_POST, jsonBuf);
        Serial.println(c ? "Publish Temperature success" : "Publish Temperature fail");

        sprintf(param, "{\"Smoke\":%.2f}", smoke);
        sprintf(jsonBuf, ALINK_BODY_FORMAT, param);
        boolean d = client.publish(ALINK_TOPIC_PROP_POST, jsonBuf);
        Serial.println(d ? "Publish Smoke success" : "Publish Smoke fail");
    }
}

void setup() {
    Serial.begin(115200);
    WiFi.begin(WIFI_SSID, WIFI_PASSWD);
    client.setServer(MQTT_SERVER, MQTT_PORT);

    mesh.setDebugMsgTypes(ERROR | STARTUP);
    mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
    mesh.onReceive(&receivedCallback);

    userScheduler.addTask(taskWifiCheck);
    userScheduler.addTask(taskSendMessage);
    userScheduler.addTask(taskMqttPost);
    userScheduler.addTask(taskMqttCheck);

    taskWifiCheck.enable();
    taskSendMessage.enable();
    taskMqttPost.enable();
    taskMqttCheck.enable();
}

void loop() {
    mesh.update();
    client.loop();
}
